/*************************************************************************
 Copyright (C) Unpublished Unis Software, Inc. All rights reserved.
 Unis Software, Inc., Confidential and Proprietary.

 This software is subject to copyright protection
 under the laws of the Public of China and other countries.

 Unless otherwise explicitly stated, this software is provided
 by Unis "AS IS".
 *************************************************************************/
package com.unis.common.secure.realm;

import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;

import org.apache.commons.beanutils.BeanUtils;
import org.apache.shiro.authc.AccountException;
import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UnknownAccountException;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.config.ConfigurationException;
import org.apache.shiro.realm.jdbc.JdbcRealm;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.util.ByteSource;
import org.apache.shiro.util.JdbcUtils;

import com.unis.model.account.SysUser;

/**
 * 系统授权
 * 
 * @author CSJ (email:raulcsj@163.com)
 * @since 2013-3-1
 */
public class JdbcRealmExt extends JdbcRealm {

    private Map<String, String> getPasswordForUser(Connection conn, String username) throws SQLException {

        Map<String, String> result = new HashMap<String, String>();
        PreparedStatement ps = null;
        ResultSet rs = null;
        try {
            ps = conn.prepareStatement(authenticationQuery);
            ps.setString(1, username);
            rs = ps.executeQuery();
            boolean foundResult = false;
            while (rs.next()) {
                if (foundResult) {
                    throw new AuthenticationException("More than one user row found for user [" + username
                            + "]. Usernames must be unique.");
                }
                ResultSetMetaData metaData = rs.getMetaData();
                int colSize = metaData.getColumnCount();
                for (int i = 1; i <= colSize; i++) {
                    String colName = metaData.getColumnLabel(i);
                    result.put(colName, rs.getString(colName));
                }
                foundResult = true;
            }
        } finally {
            JdbcUtils.closeResultSet(rs);
            JdbcUtils.closeStatement(ps);
        }

        return result;
    }

    @Override
    public AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
        UsernamePasswordToken upToken = (UsernamePasswordToken) token;
        String username = upToken.getUsername();

        if (username == null) {
            throw new AccountException("Null usernames are not allowed by this realm.");
        }

        Connection conn = null;
        SimpleAuthenticationInfo info = null;
        try {
            conn = dataSource.getConnection();

            String password = null;
            String salt = null;
            Map<String, String> result = getPasswordForUser(conn, username);
            switch (saltStyle) {
            case NO_SALT:
                password = result.get("password");
                break;
            case CRYPT:
                throw new ConfigurationException("Not implemented yet");
            case COLUMN:
                password = result.get("password");
                salt = result.get("salt");
                break;
            case EXTERNAL:
                password = result.get("salt");
                salt = getSaltForUser(username);
            }

            if (password == null) {
                throw new UnknownAccountException("No account found for user [" + username + "]");
            }

            SysUser sysUser = new SysUser();
            BeanUtils.populate(sysUser, result);
            info = new SimpleAuthenticationInfo(sysUser, password.toCharArray(), getName());

            if (salt != null) {
                info.setCredentialsSalt(ByteSource.Util.bytes(salt));
            }

        } catch (SQLException e) {
            final String message = "There was a SQL error while authenticating user [" + username + "]";
            throw new AuthenticationException(message, e);
        } catch (IllegalAccessException e) {
            throw new AuthenticationException(e);
        } catch (InvocationTargetException e) {
            throw new AuthenticationException(e);
        } finally {
            JdbcUtils.closeConnection(conn);
        }

        return info;
    }

    @Override
    public AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
        if (principals == null) {
            throw new AuthorizationException("PrincipalCollection method argument cannot be null.");
        }
        String username = ((SysUser) getAvailablePrincipal(principals)).getUserId();
        Connection conn = null;
        Set<String> roleNames = null;
        Set<String> permissions = null;
        try {
            conn = dataSource.getConnection();
            roleNames = getRoleNamesForUser(conn, username);
            if (permissionsLookupEnabled) {
                permissions = getPermissions(conn, username, roleNames);
            }
        } catch (SQLException e) {
            final String message = "There was a SQL error while authorizing user [" + username + "]";
            throw new AuthorizationException(message, e);
        } finally {
            JdbcUtils.closeConnection(conn);
        }

        SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(roleNames);
        info.setStringPermissions(permissions);
        return info;

    }
}
